package gdoku.generator;

import java.io.File;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;

import kameleon.document.Array;
import kameleon.document.BulletListElement;
import kameleon.document.Cell;
import kameleon.document.CellHeader;
import kameleon.document.Document;
import kameleon.document.ElementPropertiesDefaultNames;
import kameleon.document.HorizontalSeparator;
import kameleon.document.HyperTextLink;
import kameleon.document.LineBreak;
import kameleon.document.ListElement;
import kameleon.document.MailLink;
import kameleon.document.NumberedListElement;
import kameleon.document.Paragraph;
import kameleon.document.TextParagraphElement;
import kameleon.document.Row;
import kameleon.document.Text;
import kameleon.document.TextParagraph;
import kameleon.document.Title;
import kameleon.exception.InvalidPropertyException;
import kameleon.exception.KameleonException;

/**
 * Generates {@code DokuWiki} files.
 * 
 * <p>The syntax dor {@code DokuWiki} files can be found 
 * <a href="http://www.dokuwiki.org/syntax">here</a>.
 * 
 * @author		Dervin Cyrielle, Schnell Michaël
 * @version		1.0
 */
public class Generator implements ElementPropertiesDefaultNames, DokuWikiConstants {

	/**
	 * Charset for the generated files.
	 */
	private static final String FILE_CHARSET = "UTF-8" ;//$NON-NLS-1$

	/**
	 * Default name for the function which generates the {@code DokuWiki} code.
	 */
	private static final String GENERATE_FUNC = "generate" ;//$NON-NLS-1$

	/**
	 * Indicates how to code different properties.
	 */
	protected static HashMap<String, String> elementEncoding ;

	/**
	 * {@code PrintWriter} for output file.
	 */
	protected PrintWriter pw ;

	/**
	 * Contains the data used to fill the generated file.
	 */
	protected Document document ;

	/**
	 * //TODO add javadoc
	 */
	protected boolean removeNewLine;

	/**
	 * //TODO add javadoc
	 */
	protected boolean generateFormats;

	/**
	 * @see		Generateur#initProperties()
	 */
	static {
		Generator.initProperties() ;
	}// static

	/**
	 * Sole constructor.
	 * 
	 * @param	f
	 * 			target file for the generation
	 * 
	 * @param	d
	 * 			{@code Document} containing the data for the generated file
	 * 
	 * @throws	KameleonException 
	 * 			if there was an error while creating the {@code Writer} for the file
	 */
	public Generator(File f, Document d) throws KameleonException {
		this.document = d ;
		this.generateFormats = true ;
		try {
			this.pw = new PrintWriter(f, FILE_CHARSET) ;
		} catch (Exception ex) { 
			//TODO ajouter une exception à nous
			throw new KameleonException(ex.getMessage()) ;
		}// try
	}// Generator(File, Document)

	/**
	 * Initializes the elements map for the {@code DokuWiki} elements.
	 * 
	 * <p>To display a bold test in a {@code DokuWiki} file, you
	 * have to enclose it with **. So for the bold property, the
	 * map will contain {@code "**%s**"}.
	 */
	protected static void initProperties() {
		Generator.elementEncoding = new HashMap<String, String>() ;
		Generator.elementEncoding.put(FORMAT_BOLD,        "**%s**") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_ITALIC,      "//%s//") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_UNDERLINED,  "__%s__") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_STRUCK,      "<del>%s</del>") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_SUBSCRIPT,   "<sub>%s</sub>") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_MONOSPACE,   "''%s''") ;//$NON-NLS-1$
		Generator.elementEncoding.put(FORMAT_SUPERSCRIPT, "<sup>%s</sup>") ;//$NON-NLS-1$
		Generator.elementEncoding.put(MAIL_LINK,          "<%s>") ;//$NON-NLS-1$
		Generator.elementEncoding.put(NO_WIKI,            "<nowiki>%s</nowiki>") ;//$NON-NLS-1$
	}// initProperties()

	/**
	 * Closes the {@code Writer} for the generated file.
	 */
	public void close() {
		this.pw.close() ;
	}// close()

	/**
	 * Launches the generation of the {@code DokuWiki} file.
	 * 
	 * @throws	KameleonException
	 * 			if there was an error while generating the file
	 */
	public void generate() throws KameleonException {
		//System.out.println("Generator.generate()");
		Paragraph previous = null ;
		Iterator<Paragraph> iter = this.document.iterator() ;
		boolean hasNext = true;
		/* The document is empty. */
		if (!iter.hasNext()) {
			return ;
		}// if
		/* The document has at least one Paragraph */
		Paragraph p = iter.next() ;
		while (hasNext) {
			try {
				Method methode = Generator.class.getDeclaredMethod(
						GENERATE_FUNC, p.getClass()) ;
				methode.invoke(this, p) ;
				previous = p ;
				hasNext = iter.hasNext();
				if (hasNext) {
					p = iter.next() ;
					if (!(previous instanceof ListElement) || !(p instanceof ListElement)) {
						this.pw.print(PARAGRAPH_SEPARATOR) ;
					} else {
						this.pw.print(LIST_PARAGRAPH_SEPARATOR) ;
					}// if
				}// if
			} catch (NoSuchMethodException ex) {
				/* If no matching method was found, skip the paragraph. */
				previous = p ;
				hasNext = iter.hasNext();
				if (hasNext) {
					p = iter.next() ;
				}// if
			} catch (SecurityException ex) {
				/* If the method cannot be accessed, skip the paragraph. */
			} catch (IllegalAccessException ex) {
				/* This should not happen. */
			} catch (IllegalArgumentException e) {
				/* This should not happen. */
			}  catch (InvocationTargetException ex) {
				/* Re-throw the exception thrown by the invoked method */
				throw (KameleonException) ex.getCause() ;
			}// try
		}// for
	}// generate()

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Title}.
	 * 
	 * @param	title
	 * 			generated {@code Title}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(Title title) throws KameleonException {
		//System.out.println("Generator.generate(Title)");
		//TODO Remplacer 6 et 7 par des variables constantes
		int nEquals ;
		try {
			nEquals = 7 - (Integer) title.getProperty(TITLE_LEVEL) ;
		} catch (Exception e) {
			/* This should not happen. Use a default level. */
			nEquals = 6 ;
		}// try
		for(int equal=0; equal<nEquals; equal++) {
			this.pw.print('=') ;
		}// for
		this.pw.print(title.getProperty(TEXT_BODY)) ;
		for(int equal=0; equal<nEquals; equal++) {
			this.pw.print('=') ;
		}// for
	}// generate(Title)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code HyperTextLink}.
	 * 
	 * @param	link
	 * 			generated {@code HyperTextLink}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(HyperTextLink link) throws KameleonException {
		//System.out.println("Generator.generate(HyperTextLink)");
		this.pw.print(HYPERTEXT_LINK_START) ;
		this.pw.print(link.getProperty(LINK_DESTINATION)) ;
		TextParagraph textLink = link.getText();
		if(textLink.getCount() != 0) {
			this.pw.print(HYPERTEXT_LINK_SEPARATOR) ;
			this.generateFormats = false ;
			this.generate(textLink) ;	
			this.generateFormats = true ;
		}// if
		this.pw.print(HYPERTEXT_LINK_END) ;
	}// generate(HyperTextLink)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code MailLink}.
	 * 
	 * @param	mail
	 * 			generated {@code MailLink}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(MailLink mail) throws KameleonException {
		//System.out.println("Generator.generate(MailLink)");
		this.pw.printf(Generator.elementEncoding.get(MAIL_LINK),
				mail.getProperty(MAIL_DESTINATION)) ;
	}// generate(MailLink)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Array}.
	 * 
	 * @param	array
	 * 			generated {@code Array}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(Array array) throws KameleonException {
		//System.out.println("Generator.generate(Array)");
		for(Row row : array) {
			this.generate(row) ;
		}// for
	}// generate(Array)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Row}.
	 * 
	 * @param	row
	 * 			generated {@code Row}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(Row row) throws KameleonException {
		//System.out.println("Generator.generate(Row)");
		for(Cell cell : row) {
			try {
				Method method = Generator.class.getDeclaredMethod(
						GENERATE_FUNC, cell.getClass()) ;
				method.invoke(this, cell) ;
			} catch (NoSuchMethodException ex) {
				/* If no matching method was found, skip the cell. */
			} catch (SecurityException ex) {
				/* If the method cannot be accessed, skip the cell. */
			} catch (IllegalAccessException ex) {
				/* This should not happen. */
			} catch (IllegalArgumentException e) {
				/* This should not happen. */
			}  catch (InvocationTargetException ex) {
				/* Re-throw the exception thrown by the invoked method */
				throw (KameleonException) ex.getCause() ;
			}// try
		}// for
		this.pw.print(ROW_SEPARATOR) ;
	}// generate(Row)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Cell}.
	 * 
	 * @param	cell
	 * 			generated {@code Cell}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 * 
	 * @see		#generate(Cell, String)
	 */
	protected void generate(Cell cell) throws KameleonException {
		//System.out.println("Generator.generate(Cell)");
		this.generate(cell, CELL_START_TAG) ;
	}// generate(Cell)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code CellHeader}.
	 * 
	 * @param	header
	 * 			generated {@code CellHeader}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 * 
	 * @see		#generate(Cell, String)
	 */
	protected void generate(CellHeader header) throws KameleonException {
		//System.out.println("Generator.generate(CellHeader)");
		this.generate(header, HEADER_START_TAG) ;
	}// generate(CellHeader)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Cell}.
	 * 
	 * @param	cell
	 * 			generated {@code CellHeader}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(Cell cell, String startSymbol) throws KameleonException {
		//System.out.println("Generator.generate(Cell, String)");
		this.pw.print(startSymbol) ;
		/* Do not write cell content if a row span is found */
		if (cell.isProperty(ROW_SPAN) && cell.getProperty(ROW_SPAN).equals(true)) {
			this.pw.print(CELL_ROW_SPAN) ;
		}/* Write content only if no col span is found */
		else if (!cell.isProperty(COL_SPAN) 
				|| cell.getProperty(COL_SPAN).equals(false)) {
			/* Determine aligment */
			int textAlign = CENTER_ALIGNMENT ;
			if (cell.isProperty(TEXT_ALIGNMENT)) {
				int align = (Integer) cell.getProperty(TEXT_ALIGNMENT) ;
				if ((align == LEFT_ALIGNMENT) || (align == RIGHT_ALIGNMENT)) {
					textAlign = align ;
				}// if
			}// if
			/* Print content and alignment */
			if (textAlign != LEFT_ALIGNMENT) {
				this.pw.print(ALIGNMENT_SPECIFIER) ;
			}// if
			this.removeNewLine = true ;
			for(Paragraph p : cell) {
				try {
					Method methode = Generator.class.getDeclaredMethod(
							GENERATE_FUNC, p.getClass()) ;
					//					System.out.printf("Par: %s\n", p.getClass());
					methode.invoke(this, p) ;
				} catch (NoSuchMethodException ex) {
					/* If no matching method was found, skip the element. */
				} catch (SecurityException ex) {
					/* If the method cannot be accessed, skip the element. */
				} catch (IllegalAccessException ex) {
					/* This should not happen. */
				} catch (IllegalArgumentException e) {
					/* This should not happen. */
				}  catch (InvocationTargetException ex) {
					/* Re-throw the exception thrown by the invoked method */
					throw (KameleonException) ex.getCause() ;
				}// try
			}// for
			this.removeNewLine = false ;
			if (textAlign != RIGHT_ALIGNMENT) {
				this.pw.print(ALIGNMENT_SPECIFIER) ;
			}// if
		}// if
	}// generate(Cell)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code TextParagraph}.
	 * 
	 * @param	tp
	 * 			generated {@code TextParagraph}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	//TODO gérer paragraphes vides
	protected void generate(TextParagraph tp) throws KameleonException {
		//System.out.println("Generator.generate(TextParagraph)");
		for(TextParagraphElement pe : tp) {
			try {
				Method methode = Generator.class.getDeclaredMethod(
						GENERATE_FUNC, pe.getClass()) ;
				methode.invoke(this, pe) ;
			} catch (NoSuchMethodException ex) {
				/* If no matching method was found, skip the element. */
			} catch (SecurityException ex) {
				/* If the method cannot be accessed, skip the element. */
			} catch (IllegalAccessException ex) {
				/* This should not happen. */
			} catch (IllegalArgumentException e) {
				/* This should not happen. */
			}  catch (InvocationTargetException ex) {
				/* Re-throw the exception thrown by the invoked method */
				throw (KameleonException) ex.getCause() ;
			}// try
		}// for
	}// generate(TextParagraph)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code BulletListElement}.
	 * 
	 * @param	list
	 * 			generated {@code BulletListElement}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(BulletListElement list) throws KameleonException {
		//System.out.println("Generator.generate(BulletListElement)");
		this.generate(list, BULLET_LIST_ITEM) ;
	}// generate(BulletListElement)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code NumberedList}.
	 * 
	 * @param	list
	 * 			generated {@code NumberedList}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(NumberedListElement list) throws KameleonException {
		//System.out.println("Generator.generate(NumberedList)");
		this.generate(list, NUMBERED_LIST_ITEM) ;
	}// generate(NumberedList)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code List}.
	 * 
	 * @param	list
	 * 			generated {@code List}
	 * 
	 * @param	itemStart
	 * 			token used to specify the list type
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(ListElement list, String itemStart) throws KameleonException {
		//System.out.println("Generator.generate(List, String)");
		this.pw.print(LIST_ITEM) ;
		int level = (Integer) list.getProperty(LIST_LEVEL) ;
		for(int l=0; l<level; ++l) {
			this.pw.print(LIST_ITEM) ;
		}// for
		this.pw.print(itemStart) ;
		this.generate(list) ;
	}// generate(NumberedList)

	/**
	 * Generates the {@code DokuWiki} code for the given instance of
	 * {@code Text}.
	 * 
	 * @param	t
	 * 			generated {@code Text}
	 * 
	 * @throws	KameleonException
	 * 			if there was an exception while generating
	 */
	protected void generate(Text t) {
		//System.out.println("Generator.generate(Text)");
		String text = "" ;
		try {
			text = (String) t.getProperty(TEXT_BODY);
		} catch (InvalidPropertyException e) {
			/* This should not happen. */
		}// try
		//TODO Solution temporaire pour les lignes horizontales
		if (text.matches("[_]+")) {
			this.pw.print(PARAGRAPH_SEPARATOR) ;
			this.generate((HorizontalSeparator) null) ;
			return ;
		}// if
		//
		if (text.matches("\t")) {
			this.pw.print("<nowiki>\t</nowiki>") ;
			return ;
		}// if
		if(this.removeNewLine) {
			text = text.replaceAll("\n|\r","");
		}// if
		/* On entoure les balises de formats dokuwiki par '<nowiki>' 
		 * équivalent à <nowiki> */
		for(String dformat : SPECIAL_STRINGS) {
			text = text.replaceAll(dformat, "<nowiki>$1</nowiki>") ;
		}// for
		String format = "%s" ;
		if (this.generateFormats) {
			String[] FORMATS = {FORMAT_BOLD, FORMAT_ITALIC, FORMAT_UNDERLINED,
					FORMAT_MONOSPACE, FORMAT_STRUCK, FORMAT_SUBSCRIPT, 
					FORMAT_SUPERSCRIPT};
			for(String f : FORMATS) {
				try {
					if (t.isProperty(f) && Boolean.TRUE.equals(t.getProperty(f))) {
						format = String.format(Generator.elementEncoding.get(f), format) ;
					}// if
				} catch(InvalidPropertyException ipe) {
					/* This should not happen. */
				}// try
			}// for
		}// if
		this.pw.printf(format, text) ;
	}// generate(Text)

	/**
	 * //TODO Add javadoc
	 * @param lb
	 */
	protected void generate(LineBreak lb) {
		//System.out.println("Generator.generate(LineBreak)");
		this.pw.print(LINE_BREAK) ;
	}// generate(LineBreak)

	/**
	 * //TODO Add javadoc
	 * @param hs
	 */
	protected void generate(HorizontalSeparator hs) {
		//System.out.println("Generator.generate(HorizontalSeparator)");
		this.pw.print(HORIZONTAL_SEPARATOR) ;
	}// generate(HorizontalSeparator)

}// class Generateur 